Skip to content

Migrate cuda_pathfinder/_dynamic_libs search modules from os.path to pathlib - #2489

Open
LeSingh1 wants to merge 2 commits into
NVIDIA:mainfrom
LeSingh1:pathlib/dynamic-libs
Open

Migrate cuda_pathfinder/_dynamic_libs search modules from os.path to pathlib#2489
LeSingh1 wants to merge 2 commits into
NVIDIA:mainfrom
LeSingh1:pathlib/dynamic-libs

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 4, 2026

Copy link
Copy Markdown

Part 1 of the series proposed in #2410: cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py and search_steps.py. Deliberately small so the conversion style and the compatibility approach can be reviewed before the remaining six parts follow the same template.

What changed

Path construction, joining, and filesystem predicates now go through Path/PurePath instead of os.path string manipulation. search_platform.py no longer imports os at all.

Compatibility

Treated as a hard constraint, per the discussion on the issue. Every entry point still accepts str, and every function that documents or returns str still returns strPath is used strictly as the internal representation and converted back with str() at each return. SearchPlatform, FindResult.abs_path, and the LoadedDL.abs_path that reaches users are unchanged in both type and value. No signature changes.

Deliberately not converted

Rather than folding these in silently, both are left alone with a comment saying why:

  • _find_lib_dir_using_anchor keeps os.path.normpath. pathlib has no equivalent because PurePath intentionally does not collapse .., so converting would change the path reported for an anchor such as a CUDA_PATH containing ...
  • _derive_ctk_root_windows keeps ntpath. ntpath.dirname slices its input and preserves the caller's separators; PureWindowsPath rewrites them to backslashes. derive_ctk_root also reaches this function on Linux, so converting would return an unusable root (\opt\foo) for a POSIX path whose parent directory happens to be named bin.

glob.glob is also retained where wildcard expansion is needed — Path.glob orders results differently, and the reverse-sorted newest-first policy from #1732 depends on the current string sort.

Happy to revisit any of these if you'd prefer them handled differently; that decision will set the pattern for the rest of the series.

Verification

No behavior change intended. Beyond the test suite, I fuzzed the old and new implementations against each other: 200k randomized path shapes for the derive_ctk_root helpers, and 3000 randomized real directory trees (including trailing-slash, // and /./ spellings) for the search helpers. The only string-level divergence is that a redundant . component in an input directory is now collapsed — the new string is the normalized form of the old and names the same file — and that input cannot occur, since lib_dir always arrives already normpath-ed.

pre-commit run passes every hook at the pinned versions; mypy is clean over cuda_pathfinder/cuda.

Since I don't have CUDA hardware, I verified the suite on Linux CI rather than locally. Against upstream/main the full pytest output is byte-identical apart from elapsed time — same 1188 passed, 4 skipped, and the same 88 Not found errors for absent NVIDIA libraries, in the same order.

os.path is soft-deprecated in favor of the type-safe pathlib API (NVIDIA#2410).
Convert the two filesystem-search modules under _dynamic_libs so path
construction, joining, and predicates go through Path/PurePath instead of
os.path string manipulation, which makes it explicit which values are paths
and which are the plain strings the search cascade accumulates for error
reporting.

Public compatibility is treated as a hard constraint: every entry point still
accepts str, and every function that documents or returns str still returns
str. Path is used strictly as the internal representation and converted back
with str() at each return, so SearchPlatform, FindResult.abs_path, and the
LoadedDL.abs_path that reaches users are all unchanged in type and value.

Two sites are deliberately left on the old API rather than converted silently,
each with a comment explaining why:

- _find_lib_dir_using_anchor keeps os.path.normpath. pathlib has no equivalent
  because PurePath intentionally does not collapse "..", so converting would
  change the path reported for an anchor such as a CUDA_PATH containing "..".

- _derive_ctk_root_windows keeps ntpath. ntpath.dirname slices its input and
  preserves the caller's separators, while PureWindowsPath rewrites them to
  backslashes. derive_ctk_root also reaches this function on Linux, so
  converting would return an unusable root for a POSIX path whose parent
  directory is named "bin".

glob.glob is also kept where wildcard expansion is needed; Path.glob differs in
result ordering, and the reverse-sorted newest-first policy from NVIDIA#1732 depends
on the current string sort.

No behavior change is intended. The only string-level difference is that
redundant "." components in an input directory are now collapsed, which cannot
occur for the values these functions actually receive.

Signed-off-by: LeSingh1 <sshaurya914@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the cuda.pathfinder Everything related to the cuda.pathfinder module label Aug 4, 2026
@LeSingh1

LeSingh1 commented Aug 4, 2026

Copy link
Copy Markdown
Author

@mdboom — this is the part 1 I offered on #2410, sent as a small sample so the conversion style and the compatibility approach can be checked before the remaining six parts follow the same template. Happy to hold the rest until you've had a look, or to drop this entirely if you'd rather assign it elsewhere.

Two notes for whoever picks it up:

pr-metadata-check needs a milestone and I can't set one as an outside contributor — the cuda.pathfinder label applied but the milestone didn't. cuda.pathfinder next looks like the right one.

On verification: I don't have CUDA hardware, so I ran the suite on Linux CI rather than locally. Rather than just matching the pass count, I diffed the full pytest output against upstream/main with timestamps stripped — it's byte-identical apart from elapsed time, including the same 88 Not found errors for absent NVIDIA libraries in the same order. I also fuzzed the old and new implementations against each other (200k randomized path shapes for the derive_ctk_root helpers, 3000 randomized directory trees for the search helpers), which is what surfaced the two sites I left on os.path/ntpath and documented in the description — the ntpath one is a genuine functional regression on Linux, not a style preference.

@mdboom

mdboom commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

A few implementation comments:

Treated as a hard constraint, per the discussion on the issue. Every entry point still accepts str, and every function that documents or returns str still returns str — Path is used strictly as the internal representation and converted back with str() at each return. SearchPlatform, FindResult.abs_path, and the LoadedDL.abs_path that reaches users are unchanged in both type and value. No signature changes.

This only needs to apply to functions in the public API. We are free to change the implementation details of internal function calls as we see fit, so we should prefer pathlib.Path wherever feasible.

glob.glob is also retained where wildcard expansion is needed — Path.glob orders results differently, and the reverse-sorted newest-first policy from #1732 depends on the current string sort.

All uses of glob.glob are passed to sorted anyway, so why does the sorting order matter? In the changes here we are paying a penalty of converting between Path and str objects, so it would be preferable to stay in the pathlib.Path space if possible.

Follow-up to review feedback on NVIDIA#2489.

Narrow the str-compatibility constraint to the public API. The internal
helpers and the SearchPlatform protocol now pass and return Path; str()
is applied only where the value leaves this module:

- FindResult.abs_path, which reaches users unchanged as LoadedDL.abs_path
- derive_ctk_root(), whose result is consumed by load_nvidia_dynamic_lib
  and the header search

Replace all four glob.glob() call sites with Path.glob(). Sorting is done
on the string form via a small sorted_glob() helper: PurePath ordering is
case-insensitive on Windows, so plain sorted() over Path objects would
reorder mixed-case filenames relative to the byte-wise ordering these call
sites have always used, and the newest-first policy from NVIDIA#1732 rides on
that ordering.

Two sites deliberately stay on os.path:

- _find_lib_dir_using_anchor() keeps os.path.normpath(): PurePath does not
  collapse "..", and Path.resolve() would additionally follow symlinks.
- _derive_ctk_root_windows() keeps ntpath. derive_ctk_root() also reaches
  it on Linux, where PureWindowsPath would rewrite "/opt/foo/bin/libx.so"
  to "\opt\foo" instead of "/opt/foo".

Differential fuzzing against the previous revision (400k randomized path
shapes, 600 randomized directory trees) shows one intended change:
LinuxSearchPlatform.conda_anchor_point() now returns a normalized Path
rather than $CONDA_PREFIX verbatim. That is not observable downstream,
because _find_lib_dir_using_anchor() already normpath'd its result;
resolved paths, error messages and listdir attachments are byte-identical.

Signed-off-by: LeSingh1 <sshaurya914@gmail.com>
@LeSingh1

LeSingh1 commented Aug 4, 2026

Copy link
Copy Markdown
Author

Done in 5d5f21a.

Internals and the SearchPlatform protocol now pass and return Path. str() is applied only where a value leaves these modules: FindResult.abs_path (which surfaces unchanged as the public LoadedDL.abs_path) and derive_ctk_root(). All four glob.glob sites are now Path.glob — you were right that the sorted() wrapper makes the ordering argument moot.

One thing worth flagging: sorted() over Path compares PurePath._str_normcase, which is lowercased on Windows, so it isn't the same order as sorted() over str for mixed-case names. I kept the existing order by sorting on the string form in a small sorted_glob() helper, rather than change Windows behaviour in a PR that otherwise changes nothing — and I have no Windows machine to check it on. Happy to drop the key for simpler code if you'd prefer.

os.path.normpath in _find_lib_dir_using_anchor stays: no pathlib equivalent, since PurePath doesn't collapse .. and resolve() would also follow symlinks. _derive_ctk_root_windows stays on ntpath — I re-checked, and it still matters because derive_ctk_root reaches it on Linux, where PureWindowsPath turns /opt/foo/bin/libx.so into \opt\foo.

A correction to my earlier comment. I said the CI baseline was "1188 passed and 88 Not found errors for absent NVIDIA libraries". That was wrong, and I should have looked harder before asserting it. The 88 were fixture 'mocker' not found — my fork-side test job was missing pytest-mock, so those 88 tests never ran. The Not found: libname= lines I took for the cause were unrelated INFO logging. I've fixed the job to install the test dependency group and pinned -p no:randomly, and the real numbers are 1276 passed, 4 skipped, 0 errors, identical on the PR's base commit and on this revision — full pytest output byte-identical apart from elapsed time.

Differential fuzzing against the previous revision (400k randomized path shapes, 600 randomized directory trees) shows one intended difference: conda_anchor_point() returns a normalized Path rather than $CONDA_PREFIX verbatim. Not observable downstream, since _find_lib_dir_using_anchor already normpath'd its result.

LeSingh1 added a commit to LeSingh1/cuda-python that referenced this pull request Aug 4, 2026
Follow-up to the review feedback on NVIDIA#2489: the str-compatibility constraint
applies only to the public API.

The try_* methods and _no_such_file_in_dir now work in Path throughout. str()
is applied once, where abs_path is stored on the public LocatedStaticLib and
LocatedBitcodeLib. The relative-path constants go from os.path.join(...) to
forward-slash literals, matching how site_packages_dirs is already written in
the same dicts; Path normalizes the separator on Windows.

One behavior change: a CUDA_PATH or CONDA_PREFIX containing redundant
separators ("//", "/.") now produces a normalized abs_path, because Path
collapses them. Differential fuzzing against the pre-revision code (16k lookups
over randomized trees, comparing located paths and full error text) shows no
other difference, and none at all when those variables are free of redundant
separators.

Signed-off-by: LeSingh1 <sshaurya914@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.pathfinder Everything related to the cuda.pathfinder module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants